///! TODO
use core::fmt;
use std::{
    fmt::{Debug, Formatter},
    marker::PhantomData,
    os::raw::c_void,
    ptr,
    ptr::NonNull,
    slice::{from_raw_parts, from_raw_parts_mut},
};

use crate::{
    libspdk::{
        spdk_bdev_io, spdk_bdev_io_complete, spdk_bdev_io_get_buf, spdk_io_channel, spdk_nvme_cmd,
    },
    nvme::NvmeStatus,
    Bdev, BdevOps, IoStatus, IoType, IoVec,
};

/// Trait to cast an array-like container to an array of `IoVec`.
pub trait AsIoVecs {
    /// Casts an object to a slice of `IoVec`.
    #[inline(always)]
    fn as_io_vecs(&self) -> &[IoVec];

    /// Casts a mutable object to a mutable slice of `IoVec`.
    #[inline(always)]
    fn as_io_vecs_mut(&mut self) -> &mut [IoVec];
}

/// Wrapper for SPDK `spdk_bdev_io` data structure.
///
/// # Generic Arguments
///
/// * `BdevData`: TODO
pub struct BdevIo<BdevData>
where
    BdevData: BdevOps,
{
    /// TODO
    inner: NonNull<spdk_bdev_io>,
    /// TODO
    _data: PhantomData<BdevData>,
    #[cfg(debug_assertions)]
    _no_iov: Vec<IoVec>,
}

impl<BdevData> BdevIo<BdevData>
where
    BdevData: BdevOps,
{
    /// Returns the block device that this I/O belongs to.
    #[inline]
    pub fn bdev(&self) -> Bdev<BdevData> {
        Bdev::from_inner_ptr(self.as_ref().bdev)
    }

    /// TODO
    ///
    /// # Arguments
    ///
    /// * `prod_name`: TODO
    #[inline]
    pub fn bdev_checked(&self, prod_name: &str) -> Bdev<BdevData> {
        let b = Bdev::from_inner_ptr(self.as_ref().bdev);
        debug_assert_eq!(b.product_name(), prod_name);
        b
    }

    /// Determines the type of this I/O.
    #[inline]
    pub fn io_type(&self) -> IoType {
        (self.as_ref().type_ as u32).into()
    }

    /// Marks this I/O as successfull.
    #[inline]
    pub fn ok(&self) {
        self.io_complete(IoStatus::Success);
    }

    /// Marks the IO as failed.
    #[inline]
    pub fn fail(&self) {
        self.io_complete(IoStatus::Failed);
    }

    /// Marks the IO as impossible to submit due to a memory constraint.
    #[inline]
    pub fn no_mem(&self) {
        self.io_complete(IoStatus::NoMemory);
    }

    /// TODO
    ///
    /// # Arguments
    ///
    /// * `io_status`: TODO
    #[inline]
    pub fn io_complete(&self, io_status: IoStatus) {
        unsafe {
            spdk_bdev_io_complete(self.inner.as_ptr(), io_status.into());
        }
    }

    /// TODO
    #[inline(always)]
    pub fn iovs(&self) -> &[IoVec] {
        unsafe {
            let bdev = self.as_ref().u.bdev;
            #[cfg(debug_assertions)]
            if bdev.iovs.is_null() {
                return &self._no_iov;
            }
            from_raw_parts(bdev.iovs as *const IoVec, bdev.iovcnt as usize)
        }
    }

    /// TODO
    #[inline(always)]
    pub fn iovs_mut(&self) -> &mut [IoVec] {
        unsafe {
            let bdev = self.as_ref().u.bdev;
            from_raw_parts_mut(bdev.iovs as *mut IoVec, bdev.iovcnt as usize)
        }
    }

    /// Offset of the first block where we do the I/O on the device.
    #[inline]
    pub fn offset(&self) -> u64 {
        unsafe { self.as_ref().u.bdev.offset_blocks }
    }

    /// Number of blocks this I/O will affect.
    #[inline]
    pub fn num_blocks(&self) -> u64 {
        unsafe { self.as_ref().u.bdev.num_blocks }
    }

    /// TODO
    #[inline]
    pub fn status(&self) -> IoStatus {
        self.as_ref().internal.status.into()
    }

    /// Returns the block length for this I/O.
    #[inline]
    #[allow(dead_code)]
    pub fn block_len(&self) -> u32 {
        self.bdev().block_len()
    }

    /// Determines if the IO needs an indirect buffer this can happen for
    /// example when we do a 512 write to a 4k device.
    #[inline]
    #[allow(dead_code)]
    pub fn need_buf(&self) -> bool {
        let slice = self.iovs();
        !slice[0].is_initialized()
    }

    /// Returns a mutable reference to the driver context specific for this IO.
    #[inline]
    pub fn driver_ctx_mut<T>(&mut self) -> &mut T {
        unsafe { &mut *(self.as_mut().driver_ctx.as_mut_ptr() as *mut T) }
    }

    /// Returns a reference the driver context specific for this IO.
    #[inline]
    pub fn driver_ctx<T>(&self) -> &T {
        unsafe { &*(self.as_ref().driver_ctx.as_ptr() as *const T) }
    }

    /// TODO
    ///
    /// # Arguments
    ///
    /// * `cb`: TODO
    ///
    /// # Safety
    ///
    /// TODO
    pub unsafe fn alloc_buffer(
        &mut self,
        cb: extern "C" fn(*mut spdk_io_channel, *mut spdk_bdev_io, bool),
    ) {
        spdk_bdev_io_get_buf(
            self.as_ptr(),
            Some(cb),
            self.num_blocks() * (self.block_len() as u64),
        )
    }

    /// TODO
    #[inline]
    pub fn nvme_status(&self) -> NvmeStatus {
        NvmeStatus::from(self.as_ptr())
    }

    /// TODO
    /// NVMe passthru command
    #[inline]
    pub fn nvme_cmd(&self) -> spdk_nvme_cmd {
        unsafe { self.as_ref().u.nvme_passthru.cmd }
    }

    /// TODO
    /// raw pointer to NVMe passthru data buffer
    #[inline]
    pub fn nvme_buf(&self) -> *mut c_void {
        unsafe { self.as_ref().u.nvme_passthru.buf as *mut _ }
    }

    /// TODO
    /// NVMe passthru number of bytes to transfer
    #[inline]
    pub fn nvme_nbytes(&self) -> u64 {
        unsafe { self.as_ref().u.nvme_passthru.nbytes }
    }

    /// TODO
    #[inline]
    fn as_ref(&self) -> &spdk_bdev_io {
        unsafe { self.inner.as_ref() }
    }

    /// TODO
    #[inline]
    fn as_mut(&mut self) -> &mut spdk_bdev_io {
        unsafe { self.inner.as_mut() }
    }

    /// TODO
    #[inline]
    fn as_ptr(&self) -> *mut spdk_bdev_io {
        self.inner.as_ptr()
    }

    /// Makes a new `BdevIo` instance from a raw SPDK structure pointer.
    ///
    /// # Arguments
    ///
    /// * `bio`: TODO
    #[inline]
    pub(crate) fn from_ptr(bio: *mut spdk_bdev_io) -> Self {
        BdevIo {
            inner: NonNull::new(bio).unwrap(),
            _data: Default::default(),
            #[cfg(debug_assertions)]
            _no_iov: vec![],
        }
    }

    /// TODO
    ///
    /// # Arguments
    ///
    /// * `bio`: TODO
    #[inline]
    pub fn legacy_from_ptr(bio: *mut spdk_bdev_io) -> Self {
        Self::from_ptr(bio)
    }

    /// TODO
    #[inline]
    pub fn legacy_as_ptr(&self) -> *mut spdk_bdev_io {
        self.as_ptr()
    }
}

impl<BdevData> std::fmt::Pointer for BdevIo<BdevData>
where
    BdevData: BdevOps,
{
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        write!(f, "{:p}", self.as_ptr())
    }
}

impl<BdevData> Debug for BdevIo<BdevData>
where
    BdevData: BdevOps,
{
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        if f.alternate() {
            f.debug_struct("Bdev I/O")
                .field("bdev_module", &self.bdev().module_name())
                .field("bdev_name", &self.bdev().name())
                .field("io_type", &self.io_type())
                .field("status", &self.status())
                .field("offset", &self.offset())
                .field("num_blocks", &self.num_blocks())
                .field("ptr", &unsafe { self.legacy_as_ptr() })
                .finish()
        } else {
            write!(
                f,
                "{t:?} {s:?} {a}..{b} ({nblk}) on {bdev:?}",
                t = self.io_type(),
                s = self.status(),
                a = self.offset(),
                b = self.offset() + self.num_blocks(),
                nblk = self.num_blocks(),
                bdev = self.bdev(),
            )
        }
    }
}

impl<BdevData> Clone for BdevIo<BdevData>
where
    BdevData: BdevOps,
{
    fn clone(&self) -> Self {
        Self {
            inner: self.inner,
            _data: Default::default(),
            #[cfg(debug_assertions)]
            _no_iov: vec![],
        }
    }
}
